//
//	File: OyCPU.cpp
//
#include <stdio.h>
#include <windows.h>
#include <excpt.h>
#include "OyMath3D.h"

bool COyCPU::m_bSSE = false;
COyCPU* COyCPU::GetInstance()
{
	static COyCPU instance;
	return &instance;
}

bool COyCPU::OyMath3DInitCPU(void)
{
   CPUINFO_TYP info = GetCPUInfo();
   bool bOS = OSSupportsSSE();

   if( info.bSSE && bOS ) 
	   m_bSSE = true;
   else 
	   m_bSSE = false;

   return m_bSSE;
}

/**
 * Function to detect SSE availability in operating system.
 */
bool COyCPU::OSSupportsSSE()
{
	// try SSE instruction and look for crash
	__try
	{
		_asm xorps xmm0, xmm0
	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
		if(_exception_code() == STATUS_ILLEGAL_INSTRUCTION)
			return false;	// sse not supported by os
		return false;		// unknown exception occured
	}
      
	return true;
}

CPUINFO_TYP COyCPU::GetCPUInfo()
{
	CPUINFO_TYP info;
	char *pStr = info.vendor;
	int n=1;
	int *pn = &n;

	// set all values to 0 (false)
	memset(&info, 0, sizeof(CPUINFO_TYP));

	// 1: See if we can get CPUID and vendor 's name then
	//    check for SSE and MMX Support (vendor independant)
	__try
	{
		_asm 
		{
			mov  eax, 0          // eax=0 => CPUID returns vendor name
			CPUID                // perform CPUID function

			mov  esi,     pStr
			mov  [esi],   ebx    // first 4 chars
			mov  [esi+4], edx    // next for chars
			mov  [esi+8], ecx    // last 4 chars

			mov  eax, 1          // EAX=1 => CPUID returns feature bits
			CPUID                // perform CPUID (puts feature info to EDX)

			test edx, 04000000h  // test bit 26 for SSE2
			jz   _NOSSE2         // if test failed jump
			mov  [info.bSSE2], 1 // set to true

_NOSSE2:	test edx, 02000000h  // test bit 25 for SSE
			jz   _NOSSE          // if test failed jump
			mov  [info.bSSE], 1  // set to true

_NOSSE:		test edx, 00800000h  // test bit 23 for MMX
			jz   _EXIT1          // if test failed jump
			mov  [info.bMMX], 1  // set to true
_EXIT1:  // nothing to do anymore
		}
	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
		if(_exception_code() == STATUS_ILLEGAL_INSTRUCTION)
			return info;		// cpu inactive
		return info;			// unexpected exception occurred
	}

	// 2: See if we can get extended info (vendor independant)
	_asm 
	{
		mov  eax, 80000000h     // EAX=0x8 => CPUID returns extended features
		CPUID
		cmp  eax, 80000000h     // must be greater than 0x80
		jbe  _EXIT2             // below or equal 0x80 then jump away
		mov [info.bEXT], 1      // set to true

		mov  eax, 80000001h     // => CPUID will copy ext. feat. bits to EDX
		CPUID
		test edx, 80000000h     // 0x8 indicates 3DNow!support
		jz   _EXIT2             // if failed jump away
		mov  [info.b3DNOW], 1   // set to true
_EXIT2:
	}

	// 3: Get vendor specific stuff
	//    INTEL: CPU id
	//    AMD:   CPU id, 3dnow_ex, mmx_ex
	if( (strncmp(info.vendor, "GenuineIntel", 12) == 0) && info.bEXT )		// INTEL
	{      
		_asm 
		{
			mov  eax, 1             // => CPUID will copy ext. feat. info
			CPUID                   //    to EDX and brand id to EBX
			mov  esi,   pn          // get brand id which is only supported
			mov  [esi], ebx         // by processors > PIII/Celeron
		}
		int m=0;
		memcpy(&m, pn, sizeof(char)); // id only needs lowest 8 bits
		n = m;
	}
	else if( (strncmp(info.vendor, "AuthenticAMD", 12) == 0) && info.bEXT )	// AMD
	{  
		_asm 
		{
			mov  eax, 1             // => CPUID will copy ext. feat. info
			CPUID                   //    to EDX and brand id to EAX
			mov  esi,   pn          // get cpu type
			mov  [esi], eax

			mov  eax, 0x80000001    // => CPUID will copy ext. feat. bits
			CPUID                   //    to EDX and cpu type to EAX

			test edx, 0x40000000    // 0x4 indicates AMD extended 3DNow!
			jz   _AMD1              // if failed jump away
			mov  [info.b3DNOWEX], 1 // set to true
_AMD1:		test edx, 0x00400000    // 0x004 indicates AMD extended MMX
			jz   _AMD2              // if fail jump away
			mov  [info.bMMXEX], 1   // set to true
_AMD2:
		}
	}
	else 
	{
		if( info.bEXT )
			; /* UNKNOWN VENDOR */
		else
			; /* lack of extended features */
	}

	info.vendor[13] = '\0';                // end of string
	GetCPUName(info.name, 48, n, info.vendor); // select cpu name

	return info;
}

/**
 * Get name to found processor id. Note that this method is no
 * accurate in some cases. Never use in official release.
 */
void COyCPU::GetCPUName(char *chName, int cpuNameStrNum, int BrandID, const char *vendor) 
{
	// Intel processors
	if( strncmp(vendor, "GenuineIntel", 12) == 0 ) 
	{
		switch(BrandID) 
		{
			case 0: 
			{
				sprintf_s(chName, cpuNameStrNum, "< Pentium III/Celeron");
			} break;
			case 1: 
			{
				sprintf_s(chName, cpuNameStrNum, "Pentium Celeron (1)");
			} break;
			case 2: 
			{
				sprintf_s(chName, cpuNameStrNum, "Pentium III (2)");
			} break;
			case 3: 
			{
				sprintf_s(chName, cpuNameStrNum, "Pentium III Xeon/Celeron");
			} break;
			case 4: 
			{
				sprintf_s(chName, cpuNameStrNum, "Pentium III (4)");
			} break;
			case 6: 
			{
				sprintf_s(chName, cpuNameStrNum, "Pentium III-M");
			} break;
			case 7:
			{
				sprintf_s(chName, cpuNameStrNum, "Pentium Celeron (7)");
			} break;
			case 8: 
			{
				sprintf_s(chName, cpuNameStrNum, "Pentium IV (Genuine)");
			} break;
			case 9: 
			{
				sprintf_s(chName, cpuNameStrNum, "Pentium IV");
			} break;
			case 10:
			{
				sprintf_s(chName, cpuNameStrNum, "Pentium Celeron (10)");
			} break;
			case 11:
			{
				sprintf_s(chName, cpuNameStrNum, "Pentium Xeon / Xeon-MP");
			} break;
			case 12:
			{
				sprintf_s(chName, cpuNameStrNum, "Pentium Xeon-MP");
			} break;
			case 14:
			{
				sprintf_s(chName, cpuNameStrNum, "Pentium IV-M / Xeon");
			} break;
			case 15:
			{
				sprintf_s(chName, cpuNameStrNum, "Pentium Celeron (15)");
			} break;

			default: 
				break;
		}
	}
	// AMD processors
	else if( strncmp(vendor, "AuthenticAMD", 12) == 0 ) 
	{
		switch(BrandID) 
		{
			case 1660: 
			{
				sprintf_s(chName, cpuNameStrNum, "Athlon / Duron (Model-7)");
			} break;
			case 1644: 
			{
				sprintf_s(chName, cpuNameStrNum, "Athlon / Duron (Model-6)");
			} break;
			case 1596: 
			{
				sprintf_s(chName, cpuNameStrNum, "Athlon / Duron (Model-3)");
			} break;
			case 1612: 
			{
				sprintf_s(chName, cpuNameStrNum, "Athlon (Model-4)");
			} break;
			case 1580:
			{
				sprintf_s(chName, cpuNameStrNum, "Athlon (Model-2)");
			} break;
			case 1564:
			{
				sprintf_s(chName, cpuNameStrNum, "Athlon (Model-1)");
			} break;
			case 1463:
			{
				sprintf_s(chName, cpuNameStrNum, "K6-III (Model-9)");
			} break;
			case 1420:
			{
				sprintf_s(chName, cpuNameStrNum, "K6-2 (Model-8)");
			} break;
			case 1404: 
			{
				sprintf_s(chName, cpuNameStrNum, "K6 (Model-7)");
			} break;
			case 1388:
			{
				sprintf_s(chName, cpuNameStrNum, "K6 (Model-6)");
			} break;
			case 1340:
			{
				sprintf_s(chName, cpuNameStrNum, "K5 (Model-3)");
			} break;
			case 1324:
			{
				sprintf_s(chName, cpuNameStrNum, "K5 (Model-2)");
			} break;
			case 1308: 
			{
				sprintf_s(chName, cpuNameStrNum, "K5 (Model-1)");
			} break;
			case 1292:
			{
				sprintf_s(chName, cpuNameStrNum, "K5 (Model-0)");
			} break;

			default: 
			{ 
				sprintf_s(chName, cpuNameStrNum, "older than K5"); 
				break; 
			}
		}
	}
	return;
}